﻿using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Security;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG;

namespace Sharpmark.QQ.ChatLog
{
    [ComImport, SuppressUnmanagedCodeSecurity, Guid("0000000B-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IStorage
    {
        [return: MarshalAs(UnmanagedType.Interface)]
        IStream CreateStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
        [return: MarshalAs(UnmanagedType.Interface)]
        IStream OpenStream([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr reserved1, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
        [return: MarshalAs(UnmanagedType.Interface)]
        IStorage CreateStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.U4)] int grfMode, [In, MarshalAs(UnmanagedType.U4)] int reserved1, [In, MarshalAs(UnmanagedType.U4)] int reserved2);
        [return: MarshalAs(UnmanagedType.Interface)]
        IStorage OpenStorage([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, IntPtr pstgPriority, [In, MarshalAs(UnmanagedType.U4)] int grfMode, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.U4)] int reserved);
        void CopyTo(int ciidExclude, [In, MarshalAs(UnmanagedType.LPArray)] Guid[] rgiidExclude, IntPtr snbExclude, [In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest);
        void MoveElementTo([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In, MarshalAs(UnmanagedType.Interface)] IStorage pstgDest, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName, [In, MarshalAs(UnmanagedType.U4)] int grfFlags);
        void Commit(int grfCommitFlags);
        void Revert();
        int EnumElements([In, MarshalAs(UnmanagedType.U4)] int reserved1, IntPtr reserved2, [In, MarshalAs(UnmanagedType.U4)] int reserved3, [MarshalAs(UnmanagedType.Interface)] out IEnumSTATSTG ppenum);
        void DestroyElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsName);
        void RenameElement([In, MarshalAs(UnmanagedType.BStr)] string pwcsOldName, [In, MarshalAs(UnmanagedType.BStr)] string pwcsNewName);
        void SetElementTimes([In, MarshalAs(UnmanagedType.BStr)] string pwcsName, [In] FILETIME pctime, [In] FILETIME patime, [In] FILETIME pmtime);
        void SetClass(ref Guid clsid);
        void SetStateBits(int grfStateBits, int grfMask);
        int Stat(out STATSTG pStatStg, int grfStatFlag);
    }

    [ComImport, Guid("0000000D-0000-0000-C000-000000000046"), SuppressUnmanagedCodeSecurity, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IEnumSTATSTG
    {
        [PreserveSig]
        int Next(int celt, out STATSTG rgVar, out int pceltFetched);
        [PreserveSig]
        int Skip(int celt);
        [PreserveSig]
        int Reset();
        int Clone(out IEnumSTATSTG newEnum);
    }

    public class Ole32
    {
        // Methods
        [DllImport("Ole32.dll")]
        public static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string wcsName, IStorage pstgPriority, int grfMode, IntPtr snbExclude, int reserved, out IStorage storage);
    }

    public class IStorageWrapper : IBaseStorageWrapper
    {
        // Methods
        public IStorageWrapper(string workPath)
        {
            Ole32.StgOpenStorage(workPath, null, 0x10, IntPtr.Zero, 0, out _storage);
            IBaseStorageWrapper.BaseUrl = workPath;
            STATSTG pStatStg = new STATSTG();
            base._storage.Stat(out pStatStg, 1);
            base.EnumIStorageObject(base._storage);

        }
    }

    public class IBaseStorageWrapper
    {
        // Fields
        private static string _baseUrl;
        public FileObjects FoCollection = new FileObjects();
        protected IStorage _storage;

        // Methods
        protected void EnumIStorageObject(IStorage stgArgument)
        {
            EnumIStorageObject(stgArgument, "");
        }

        protected void EnumIStorageObject(IStorage stgArgument, string basePath)
        {
            IEnumSTATSTG mstatstg;
            STATSTG statstg;
            int num;
            stgArgument.EnumElements(0, IntPtr.Zero, 0, out mstatstg);
            mstatstg.Reset();
            while (mstatstg.Next(1, out statstg, out num) == 0)
            {
                FileObjects.FileObject fo = new FileObjects.FileObject { FileType = statstg.type };
                switch (statstg.type)
                {
                    case 1:
                        {
                            IStorage storage = stgArgument.OpenStorage(statstg.pwcsName, IntPtr.Zero, 0x10, IntPtr.Zero, 0);
                            if (storage != null)
                            {
                                string path = basePath + statstg.pwcsName;
                                fo.FileStorage = storage;
                                fo.FilePath = basePath;
                                fo.FileName = statstg.pwcsName;
                                FoCollection.Add(fo);
                                EnumIStorageObject(storage, path);
                            }
                            break;
                        }
                    case 2:
                        {
                            IStream stream = stgArgument.OpenStream(statstg.pwcsName, IntPtr.Zero, 0x10, 0);
                            fo.FilePath = basePath;
                            fo.FileName = statstg.pwcsName;
                            fo.FileStream = stream;
                            FoCollection.Add(fo);
                            break;
                        }
                    case 3:
                        //Console.WriteLine("[Property:ILockBytes] Ignoring...");
                        break;

                    case 4:
                        //Console.WriteLine("[Property:IProperty] Ignoring...");
                        break;

                    default:
                        //Console.WriteLine("Unknown object, skipping and continuing...");
                        break;
                }
                if (statstg.type == 1)
                {
                    //Console.WriteLine("Type: STORAGE");
                }
            }
        }

        // Properties
        public static string BaseUrl
        {
            get
            {
                return _baseUrl;
            }
            set
            {
                _baseUrl = "mk:@MSITStore:" + value + "::/";
            }
        }

        // Nested Types
        public class FileObjects : CollectionBase
        {
            // Methods
            public void Add(FileObject fo)
            {
                base.List.Add(fo);
            }

            public FileObject Item(int index)
            {
                return (FileObject)base.List[index];
            }

            public void Remove(int index)
            {
                if ((index < (base.Count - 1)) && (index > 0))
                {
                    base.List.RemoveAt(index);
                }
            }

            // Nested Types
            public class FileObject : Stream
            {
                // Fields
                //private string _fileUrl;

                // Methods
                public override void Close()
                {
                    if (FileStream != null)
                    {
                        FileStream.Commit(0);
                        Marshal.ReleaseComObject(FileStream);
                        FileStream = null;
                        GC.SuppressFinalize(this);
                    }
                }

                public override void Flush()
                {
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    FileStream.Commit(0);
                }

                public override int Read(byte[] buffer, int offset, int count)
                {
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    int length = 0;
                    object obj2 = length;
                    GCHandle handle = new GCHandle();
                    try
                    {
                        handle = GCHandle.Alloc(obj2, GCHandleType.Pinned);
                        IntPtr pcbRead = handle.AddrOfPinnedObject();
                        if (offset != 0)
                        {
                            byte[] pv = new byte[count - 1];
                            FileStream.Read(pv, count, pcbRead);
                            length = (int)obj2;
                            Array.Copy(pv, 0, buffer, offset, length);
                            return length;
                        }
                        FileStream.Read(buffer, count, pcbRead);
                        length = (int)obj2;
                    }
                    finally
                    {
                        if (handle.IsAllocated)
                        {
                            handle.Free();
                        }
                    }
                    return length;
                }

                public string ReadFromFile()
                {
                    int num;
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    Stream stream = new MemoryStream();
                    byte[] buffer = new byte[Length];
                    Seek(0L, SeekOrigin.Begin);
                    while ((num = Read(buffer, 0, 0x400)) > 0)
                    {
                        stream.Write(buffer, 0, num);
                    }
                    stream.Seek(0L, SeekOrigin.Begin);
                    StreamReader reader = new StreamReader(stream);
                    return reader.ReadToEnd();
                }

                public void Save(string fileName)
                {
                    int num;
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    byte[] buffer = new byte[Length];
                    Seek(0L, SeekOrigin.Begin);
                    Stream stream = File.OpenWrite(fileName);
                    while ((num = Read(buffer, 0, 0x400)) > 0)
                    {
                        stream.Write(buffer, 0, num);
                    }
                    stream.Close();
                }

                public override long Seek(long offset, SeekOrigin origin)
                {
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    long num = 0L;
                    object obj2 = num;
                    GCHandle handle = new GCHandle();
                    try
                    {
                        handle = GCHandle.Alloc(obj2, GCHandleType.Pinned);
                        IntPtr plibNewPosition = handle.AddrOfPinnedObject();
                        FileStream.Seek(offset, (int)origin, plibNewPosition);
                        num = (long)obj2;
                    }
                    finally
                    {
                        if (handle.IsAllocated)
                        {
                            handle.Free();
                        }
                    }
                    return num;
                }

                public override void SetLength(long value)
                {
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    FileStream.SetSize(value);
                }

                public override void Write(byte[] buffer, int offset, int count)
                {
                    if (FileStream == null)
                    {
                        throw new ObjectDisposedException("theStream");
                    }
                    if (offset != 0)
                    {
                        int length = buffer.Length - offset;
                        byte[] destinationArray = new byte[length];
                        Array.Copy(buffer, offset, destinationArray, 0, length);
                        FileStream.Write(destinationArray, length, IntPtr.Zero);
                    }
                    else
                    {
                        FileStream.Write(buffer, count, IntPtr.Zero);
                    }
                }

                // Properties
                public override bool CanRead
                {
                    get
                    {
                        return (FileStream != null);
                    }
                }

                public override bool CanSeek
                {
                    get
                    {
                        return true;
                    }
                }

                public override bool CanWrite
                {
                    get
                    {
                        return true;
                    }
                }

                public string FileName { get; set; }

                public string FilePath { get; set; }

                public IStorage FileStorage { get; set; }

                public IStream FileStream { get; set; }

                public int FileType { get; set; }

                public string FileUrl
                {
                    get
                    {
                        return (IBaseStorageWrapper.BaseUrl + FilePath.Replace(@"\", "/") + "/" + FileName);
                    }
                    //set
                    //{
                    //    _fileUrl = value;
                    //}
                }

                public override long Length
                {
                    get
                    {
                        STATSTG statstg;
                        if (FileStream == null)
                        {
                            throw new ObjectDisposedException("theStream");
                        }
                        FileStream.Stat(out statstg, 1);
                        return statstg.cbSize;
                    }
                }

                public override long Position
                {
                    get
                    {
                        return Seek(0L, SeekOrigin.Current);
                    }
                    set
                    {
                        Seek(value, SeekOrigin.Begin);
                    }
                }
            }
        }
    }



}
